use std::{env, fs, io::Write, path::Path};

fn main() {
    // 任何变化都触发重建
    println!("cargo:rerun-if-changed=src/problems");

    let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
    let problems_dir = Path::new(&manifest_dir).join("src").join("problems");

    let mut items: Vec<(u32, String)> = fs::read_dir(&problems_dir)
        .expect("Failed to read src/problems directory")
        .filter_map(|e| e.ok())
        .filter_map(|e| {
            let path = e.path();
            if !path.is_file() {
                return None;
            }
            let name = path.file_name()?.to_string_lossy().to_string();
            // 匹配 p{digits}.rs
            if !name.starts_with('p') || !name.ends_with(".rs") || name == "mod.rs" {
                return None;
            }
            let stem = path.file_stem()?.to_string_lossy();
            // 提取数字部分
            let digits: String = stem
                .chars()
                .skip(1)
                .take_while(|c| c.is_ascii_digit())
                .collect();
            if digits.is_empty() {
                return None;
            }
            let id_num: u32 = digits.parse().ok()?;
            Some((id_num, stem.to_string()))
        })
        .collect();

    // 排序，保证生成文件稳定
    items.sort_by_key(|(id, _)| *id);

    let mut out = String::new();
    out.push_str("///! @generated by build.rs — DO NOT EDIT MANUALLY\n");
    out.push_str("///! 题目模块与注册表（自动生成）\n\n");
    out.push_str("use crate::Problem;\n\n");

    // 使用 #[path = "..."] 指向源码文件，避免内联模块中 inner doc 的限制
    for (_, module_name) in &items {
        let file_path = problems_dir.join(format!("{name}.rs", name = module_name));
        let mut lit = file_path.display().to_string();
        // 转义反斜杠，避免 Windows 路径在字符串字面量中出错
        lit = lit.replace('\\', "\\\\");
        out.push_str(&format!(
            "#[path = \"{path}\"]\npub mod {name};\n",
            path = lit,
            name = module_name
        ));
    }

    // 生成 REGISTRY（通过 Problem trait 提供强约束）
    out.push_str("\n#[allow(dead_code)]\n");
    out.push_str("pub struct ProblemEntry {\n    pub id: &'static str,\n    pub run: fn(),\n    pub title: &'static str,\n}\n\n");
    out.push_str("pub static REGISTRY: &[ProblemEntry] = &[\n");
    for (id, module_name) in &items {
        out.push_str(&format!(
            "    ProblemEntry {{ id: \"{id}\", run: <{m}::Solution as Problem>::run, title: <{m}::Solution as Problem>::TITLE }},\n",
            id = id,
            m = module_name
        ));
        // 确保类型可见（如果没被引用，某些配置下可能被优化掉），不过静态引用已足够
    }
    out.push_str("];\n");

    // 生成 dispatch 函数
    out.push_str("\n/// 根据题号调度对应题目的运行函数\n");
    out.push_str("pub fn dispatch(id: &str) -> Option<fn()> {\n    REGISTRY.iter().find(|e| e.id == id).map(|e| e.run)\n}\n");

    let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
    let out_path = Path::new(&out_dir).join("problems_registry.rs");

    // 写入文件
    let mut file = fs::File::create(&out_path).expect("Failed to create generated registry file");
    file.write_all(out.as_bytes())
        .expect("Failed to write generated registry file");
}
